/****************************************************************************
 * Copyright (c) 1998 Free Software Foundation, Inc.                        *
 *                                                                          *
 * Permission is hereby granted, free of charge, to any person obtaining a  *
 * copy of this software and associated documentation files (the            *
 * "Software"), to deal in the Software without restriction, including      *
 * without limitation the rights to use, copy, modify, merge, publish,      *
 * distribute, distribute with modifications, sublicense, and/or sell       *
 * copies of the Software, and to permit persons to whom the Software is    *
 * furnished to do so, subject to the following conditions:                 *
 *                                                                          *
 * The above copyright notice and this permission notice shall be included  *
 * in all copies or substantial portions of the Software.                   *
 *                                                                          *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
 *                                                                          *
 * Except as contained in this notice, the name(s) of the above copyright   *
 * holders shall not be used in advertising or otherwise to promote the     *
 * sale, use or other dealings in this Software without prior written       *
 * authorization.                                                           *
 ****************************************************************************/

/****************************************************************************
 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
 ****************************************************************************/


/*
 * Termcap compatibility support
 *
 * If your OS integrator didn't install a terminfo database, you can call
 * _nc_read_termcap_entry() to support reading and translating capabilities
 * from the system termcap file.  This is a kludge; it will bulk up and slow
 * down every program that uses ncurses, and translated termcap entries cannot
 * use full terminfo capabilities.  Don't use it unless you absolutely have to;
 * instead, get your system people to run tic(1) from root on the terminfo
 * master included with ncurses to translate it into a terminfo database.
 *
 * If USE_GETCAP is enabled, we use what is effectively a copy of the 4.4BSD
 * getcap code to fetch entries.  There are disadvantages to this; mainly that
 * getcap(3) does its own resolution, meaning that entries read in in this way
 * can't reference the terminfo tree.  The only thing it buys is faster startup
 * time, getcap(3) is much faster than our tic parser.
 */

#include <curses.priv.h>

#include <ctype.h>
#include <term.h>
#include <tic.h>
#include <term_entry.h>

#if HAVE_FCNTL_H
#include <fcntl.h>
#endif

MODULE_ID("$Id: read_termcap.c 153052 2008-08-13 01:17:50Z coreos $")

#ifndef PURE_TERMINFO

#ifdef __EMX__
#define is_pathname(s) ((((s) != 0) && ((s)[0] == '/')) \
		  || (((s)[0] != 0) && ((s)[1] == ':')))
#else
#define is_pathname(s) ((s) != 0 && (s)[0] == '/')
#endif

#define TC_SUCCESS     0
#define TC_UNRESOLVED -1
#define TC_NOT_FOUND  -2
#define TC_SYS_ERR    -3
#define TC_REF_LOOP   -4

#if USE_GETCAP

#if HAVE_BSD_CGETENT
#define _nc_cgetcap   cgetcap
#define _nc_cgetent(buf, oline, db_array, name) cgetent(buf, db_array, name)
#define _nc_cgetmatch cgetmatch
#define _nc_cgetset   cgetset
#else
static int _nc_cgetmatch(char *, const char *);
static int _nc_getent(char **, unsigned int *, int *, int, char **, int, const char *, int, char *);
static int _nc_nfcmp(const char *, char *);

/*-
 * Copyright (c) 1992, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Casey Leedom of Lawrence Livermore National Laboratory.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* static char sccsid[] = "@(#)getcap.c	8.3 (Berkeley) 3/25/94"; */

#define	BFRAG		1024
#define	BSIZE		1024
#define	ESC		('[' & 037)	/* ASCII ESC */
#define	MAX_RECURSION	32		/* maximum getent recursion */
#define	SFRAG		100		/* cgetstr mallocs in SFRAG chunks */

#define RECOK	(char)0
#define TCERR	(char)1
#define	SHADOW	(char)2

static size_t	 topreclen;	/* toprec length */
static char	*toprec;	/* Additional record specified by cgetset() */
static int	 gottoprec;	/* Flag indicating retrieval of toprecord */

/*
 * Cgetset() allows the addition of a user specified buffer to be added to the
 * database array, in effect "pushing" the buffer on top of the virtual
 * database.  0 is returned on success, -1 on failure.
 */
static int
_nc_cgetset(const char *ent)
{
	if (ent == 0) {
		FreeIfNeeded(toprec);
		toprec = 0;
		topreclen = 0;
		return (0);
	}
	topreclen = strlen(ent);
	if ((toprec = malloc (topreclen + 1)) == 0) {
		errno = ENOMEM;
		return (-1);
	}
	gottoprec = 0;
	(void)strcpy(toprec, ent);
	return (0);
}

/*
 * Cgetcap searches the capability record buf for the capability cap with type
 * `type'.  A pointer to the value of cap is returned on success, 0 if the
 * requested capability couldn't be found.
 *
 * Specifying a type of ':' means that nothing should follow cap (:cap:).  In
 * this case a pointer to the terminating ':' or NUL will be returned if cap is
 * found.
 *
 * If (cap, '@') or (cap, terminator, '@') is found before (cap, terminator)
 * return 0.
 */
static char *
_nc_cgetcap(char *buf, const char *cap, int type)
{
	register const char *cp;
	register char *bp;

	bp = buf;
	for (;;) {
		/*
		 * Skip past the current capability field - it's either the
		 * name field if this is the first time through the loop, or
		 * the remainder of a field whose name failed to match cap.
		 */
		for (;;) {
			if (*bp == '\0')
				return (0);
			else if (*bp++ == ':')
				break;
		}

		/*
		 * Try to match (cap, type) in buf.
		 */
		for (cp = cap; *cp == *bp && *bp != '\0'; cp++, bp++)
			continue;
		if (*cp != '\0')
			continue;
		if (*bp == '@')
			return (0);
		if (type == ':') {
			if (*bp != '\0' && *bp != ':')
				continue;
			return(bp);
		}
		if (*bp != type)
			continue;
		bp++;
		return (*bp == '@' ? 0 : bp);
	}
	/* NOTREACHED */
}

/*
 * Cgetent extracts the capability record name from the NULL terminated file
 * array db_array and returns a pointer to a malloc'd copy of it in buf.  Buf
 * must be retained through all subsequent calls to cgetcap, cgetnum, cgetflag,
 * and cgetstr, but may then be freed.
 *
 * Returns:
 *
 * positive #    on success (i.e., the index in db_array)
 * TC_UNRESOLVED if we had too many recurrences to resolve
 * TC_NOT_FOUND  if the requested record couldn't be found
 * TC_SYS_ERR    if a system error was encountered (e.g.,couldn't open a file)
 * TC_REF_LOOP   if a potential reference loop is detected
 */
static int
_nc_cgetent(char **buf, int *oline, char **db_array, const char *name)
{
	unsigned int dummy;

	return (_nc_getent(buf, &dummy, oline, 0, db_array, -1, name, 0, 0));
}

/*
 * Getent implements the functions of cgetent.  If fd is non-negative,
 * *db_array has already been opened and fd is the open file descriptor.  We
 * do this to save time and avoid using up file descriptors for tc=
 * recursions.
 *
 * Getent returns the same success/failure codes as cgetent.  On success, a
 * pointer to a malloc'd capability record with all tc= capabilities fully
 * expanded and its length (not including trailing ASCII NUL) are left in
 * *cap and *len.
 *
 * Basic algorithm:
 *	+ Allocate memory incrementally as needed in chunks of size BFRAG
 *	  for capability buffer.
 *	+ Recurse for each tc=name and interpolate result.  Stop when all
 *	  names interpolated, a name can't be found, or depth exceeds
 *	  MAX_RECURSION.
 */
static int
_nc_getent(
	char **cap,         /* termcap-content */
	unsigned int *len,  /* length, needed for recursion */
	int *beginning,     /* line-number at match */
	int in_array,       /* index in 'db_array[] */
	char **db_array,    /* list of files to search */
	int fd,
	const char *name,
	int depth,
	char *nfield)
{
	register char *r_end, *rp;
	int myfd = FALSE;
	char *record;
	int tc_not_resolved;
	int current;
	int lineno;

	/*
	 * Return with ``loop detected'' error if we've recurred more than
	 * MAX_RECURSION times.
	 */
	if (depth > MAX_RECURSION)
		return (TC_REF_LOOP);

	/*
	 * Check if we have a top record from cgetset().
	 */
	if (depth == 0 && toprec != 0 && _nc_cgetmatch(toprec, name) == 0) {
		if ((record = malloc (topreclen + BFRAG)) == 0) {
			errno = ENOMEM;
			return (TC_SYS_ERR);
		}
		(void)strcpy(record, toprec);
		rp = record + topreclen + 1;
		r_end = rp + BFRAG;
		current = in_array;
	} else {
		int foundit;

		/*
		 * Allocate first chunk of memory.
		 */
		if ((record = malloc(BFRAG)) == 0) {
			errno = ENOMEM;
			return (TC_SYS_ERR);
		}
		rp = r_end = record + BFRAG;
		foundit = FALSE;

		/*
		 * Loop through database array until finding the record.
		 */
		for (current = in_array; db_array[current] != 0; current++) {
			int eof = FALSE;

			/*
			 * Open database if not already open.
			 */
			if (fd >= 0) {
				(void)lseek(fd, (off_t)0, SEEK_SET);
			} else {
				fd = open(db_array[current], O_RDONLY, 0);
				if (fd < 0) {
					/* No error on unfound file. */
					if (errno == ENOENT)
						continue;
					free(record);
					return (TC_SYS_ERR);
				}
				myfd = TRUE;
			}
			lineno = 0;

			/*
			 * Find the requested capability record ...
			 */
			{
				char buf[2048];
				register char *b_end = buf;
				register char *bp = buf;
				register int c;

				/*
				 * Loop invariants:
				 *	There is always room for one more character in record.
				 *	R_end always points just past end of record.
				 *	Rp always points just past last character in record.
				 *	B_end always points just past last character in buf.
				 *	Bp always points at next character in buf.
				 */

				for (;;) {
					int first = lineno + 1;

					/*
					 * Read in a line implementing (\, newline)
					 * line continuation.
					 */
					rp = record;
					for (;;) {
						if (bp >= b_end) {
							int n;

							n = read(fd, buf, sizeof(buf));
							if (n <= 0) {
								if (myfd)
									(void)close(fd);
								if (n < 0) {
									free(record);
									return (TC_SYS_ERR);
								}
								fd = -1;
								eof = TRUE;
								break;
							}
							b_end = buf+n;
							bp = buf;
						}

						c = *bp++;
						if (c == '\n') {
							lineno++;
							if (rp == record || *(rp-1) != '\\')
								break;
						}
						*rp++ = c;

						/*
						 * Enforce loop invariant: if no room
						 * left in record buffer, try to get
						 * some more.
						 */
						if (rp >= r_end) {
							unsigned int pos;
							size_t newsize;

							pos = rp - record;
							newsize = r_end - record + BFRAG;
							record = realloc(record, newsize);
							if (record == 0) {
								errno = ENOMEM;
								if (myfd)
									(void)close(fd);
								return (TC_SYS_ERR);
							}
							r_end = record + newsize;
							rp = record + pos;
						}
					}
					/* loop invariant lets us do this */
					*rp++ = '\0';

					/*
					 * If encountered eof check next file.
					 */
					if (eof)
						break;

					/*
					 * Toss blank lines and comments.
					 */
					if (*record == '\0' || *record == '#')
						continue;

					/*
					 * See if this is the record we want ...
					 */
					if (_nc_cgetmatch(record, name) == 0
					 && (nfield == 0
					  || !_nc_nfcmp(nfield, record))) {
						foundit = TRUE;
						*beginning = first;
						break;	/* found it! */
					}
				}
			}
			if (foundit)
				break;
		}

		if (!foundit)
			return (TC_NOT_FOUND);
	}

	/*
	 * Got the capability record, but now we have to expand all tc=name
	 * references in it ...
	 */
	{
		register char *newicap, *s;
		register int newilen;
		unsigned int ilen;
		int diff, iret, tclen, oline;
		char *icap, *scan, *tc, *tcstart, *tcend;

		/*
		 * Loop invariants:
		 *	There is room for one more character in record.
		 *	R_end points just past end of record.
		 *	Rp points just past last character in record.
		 *	Scan points at remainder of record that needs to be
		 *	scanned for tc=name constructs.
		 */
		scan = record;
		tc_not_resolved = FALSE;
		for (;;) {
			if ((tc = _nc_cgetcap(scan, "tc", '=')) == 0)
				break;

			/*
			 * Find end of tc=name and stomp on the trailing `:'
			 * (if present) so we can use it to call ourselves.
			 */
			s = tc;
			while (*s != '\0') {
				if (*s++ == ':') {
					*(s - 1) = '\0';
					break;
				}
			}
			tcstart = tc - 3;
			tclen = s - tcstart;
			tcend = s;

			iret = _nc_getent(&icap, &ilen, &oline, current, db_array, fd, tc, depth+1, 0);
			newicap = icap;		/* Put into a register. */
			newilen = ilen;
			if (iret != TC_SUCCESS) {
				/* an error */
				if (iret < TC_NOT_FOUND) {
					if (myfd)
						(void)close(fd);
					free(record);
					return (iret);
				}
				if (iret == TC_UNRESOLVED)
					tc_not_resolved = TRUE;
				/* couldn't resolve tc */
				if (iret == TC_NOT_FOUND) {
					*(s - 1) = ':';
					scan = s - 1;
					tc_not_resolved = TRUE;
					continue;
				}
			}

			/* not interested in name field of tc'ed record */
			s = newicap;
			while (*s != '\0' && *s++ != ':')
				;
			newilen -= s - newicap;
			newicap = s;

			/* make sure interpolated record is `:'-terminated */
			s += newilen;
			if (*(s-1) != ':') {
				*s = ':';	/* overwrite NUL with : */
				newilen++;
			}

			/*
			 * Make sure there's enough room to insert the
			 * new record.
			 */
			diff = newilen - tclen;
			if (diff >= r_end - rp) {
				unsigned int pos, tcpos, tcposend;
				size_t newsize;

				pos = rp - record;
				newsize = r_end - record + diff + BFRAG;
				tcpos = tcstart - record;
				tcposend = tcend - record;
				record = realloc(record, newsize);
				if (record == 0) {
					errno = ENOMEM;
					if (myfd)
						(void)close(fd);
					free(icap);
					return (TC_SYS_ERR);
				}
				r_end = record + newsize;
				rp = record + pos;
				tcstart = record + tcpos;
				tcend = record + tcposend;
			}

			/*
			 * Insert tc'ed record into our record.
			 */
			s = tcstart + newilen;
			memmove(s, tcend, (size_t)(rp - tcend));
			memmove(tcstart, newicap, (size_t)newilen);
			rp += diff;
			free(icap);

			/*
			 * Start scan on `:' so next cgetcap works properly
			 * (cgetcap always skips first field).
			 */
			scan = s-1;
		}
	}

	/*
	 * Close file (if we opened it), give back any extra memory, and
	 * return capability, length and success.
	 */
	if (myfd)
		(void)close(fd);
 	*len = rp - record - 1;	/* don't count NUL */
	if (r_end > rp) {
		if ((record = realloc(record, (size_t)(rp - record))) == 0) {
			errno = ENOMEM;
			return (TC_SYS_ERR);
		}
	}

	*cap = record;
	if (tc_not_resolved)
		return (TC_UNRESOLVED);
	return (current);
}

/*
 * Cgetmatch will return 0 if name is one of the names of the capability
 * record buf, -1 if not.
 */
static int
_nc_cgetmatch(char *buf, const char *name)
{
	register const char *np;
	register char *bp;

	/*
	 * Start search at beginning of record.
	 */
	bp = buf;
	for (;;) {
		/*
		 * Try to match a record name.
		 */
		np = name;
		for (;;) {
			if (*np == '\0') {
				if (*bp == '|' || *bp == ':' || *bp == '\0')
					return (0);
				else
					break;
			} else if (*bp++ != *np++) {
				break;
			}
		}

		/*
		 * Match failed, skip to next name in record.
		 */
		bp--;	/* a '|' or ':' may have stopped the match */
		for (;;) {
			if (*bp == '\0' || *bp == ':')
				return (-1);	/* match failed totally */
			else if (*bp++ == '|')
				break;	/* found next name */
		}
	}
}

/*
 * Compare name field of record.
 */
static int
_nc_nfcmp(const char *nf, char *rec)
{
	char *cp, tmp;
	int ret;

	for (cp = rec; *cp != ':'; cp++)
		;

	tmp = *(cp + 1);
	*(cp + 1) = '\0';
	ret = strcmp(nf, rec);
	*(cp + 1) = tmp;

	return (ret);
}
#endif /* HAVE_BSD_CGETENT */

/*
 * Since ncurses provides its own 'tgetent()', we cannot use the native one.
 * So we reproduce the logic to get down to cgetent() -- or our cut-down
 * version of that -- to circumvent the problem of configuring against the
 * termcap library.
 */
#define USE_BSD_TGETENT 1

#if USE_BSD_TGETENT
/*
 * Copyright (c) 1980, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* static char sccsid[] = "@(#)termcap.c	8.1 (Berkeley) 6/4/93" */

#define	PBUFSIZ		512	/* max length of filename path */
#define	PVECSIZ		32	/* max number of names in path */
#define TBUFSIZ (2048*2)

static char *tbuf;

/*
 * On entry, srcp points to a non ':' character which is the beginning of the
 * token, if any.  We'll try to return a string that doesn't end with a ':'.
 */
static char *
get_tc_token(char **srcp, int *endp)
{
	int ch;
	bool found = FALSE;
	char *s, *base;
	char *tok = 0;

	*endp = TRUE;
	for (s = base = *srcp; *s != '\0'; ) {
		ch = *s++;
		if (ch == '\\') {
			if (*s == '\0') {
				break;
			} else if (*s++ == '\n') {
				while (isspace(*s))
					s++;
			} else {
				found = TRUE;
			}
		} else if (ch == ':') {
			if (found) {
				tok = base;
				s[-1] = '\0';
				*srcp = s;
				*endp = FALSE;
				break;
			}
			base = s;
		} else if (isgraph(ch)) {
			found = TRUE;
		}
	}

	/* malformed entry may end without a ':' */
	if (tok == 0 && found) {
		tok = base;
	}

	return tok;
}

static char *
copy_tc_token(char *dst, const char *src, size_t len)
{
	int ch;

	while ((ch = *src++) != '\0') {
		if (ch == '\\' && *src == '\n') {
			while (isspace(*src))
				src++;
			continue;
		}
		if (--len == 0) {
			dst = 0;
			break;
		}
		*dst++ = ch;
	}
	return dst;
}

/*
 * Get an entry for terminal name in buffer bp from the termcap file.
 */
static int
_nc_tgetent(char *bp, char **sourcename, int *lineno, const char *name)
{
	static char *the_source;

	register char *p;
	register char *cp;
	char  *dummy;
	char **fname;
	char  *home;
	int    i;
	char   pathbuf[PBUFSIZ];	/* holds raw path of filenames */
	char  *pathvec[PVECSIZ];	/* to point to names in pathbuf */
	char **pvec;			/* holds usable tail of path vector */
	char  *termpath;

	fname = pathvec;
	pvec = pathvec;
	tbuf = bp;
	p = pathbuf;
	cp = getenv("TERMCAP");

	/*
	 * TERMCAP can have one of two things in it.  It can be the name of a
	 * file to use instead of /etc/termcap.  In this case it better start
	 * with a "/".  Or it can be an entry to use so we don't have to read
	 * the file.  In this case it has to already have the newlines crunched
	 * out.  If TERMCAP does not hold a file name then a path of names is
	 * searched instead.  The path is found in the TERMPATH variable, or
	 * becomes "$HOME/.termcap /etc/termcap" if no TERMPATH exists.
	 */
	if (!is_pathname(cp)) {	/* no TERMCAP or it holds an entry */
		if ((termpath = getenv("TERMPATH")) != 0) {
			strncpy(pathbuf, termpath, sizeof(pathbuf)-1);
		} else {
			if ((home = getenv("HOME")) != 0) { /* setup path */
				p += strlen(home);	/* path, looking in */
				strcpy(pathbuf, home);	/* $HOME first */
				*p++ = '/';
			}	/* if no $HOME look in current directory */
#define	MY_PATH_DEF	".termcap /etc/termcap /usr/share/misc/termcap"
			strncpy(p, MY_PATH_DEF, (size_t)(PBUFSIZ - (p - pathbuf)));
		}
	}
	else				/* user-defined name in TERMCAP */
		strncpy(pathbuf, cp, PBUFSIZ);	/* still can be tokenized */

	*fname++ = pathbuf;	/* tokenize path into vector of names */
	while (*++p) {
		if (*p == ' ' || *p == ':') {
			*p = '\0';
			while (*++p)
				if (*p != ' ' && *p != ':')
					break;
			if (*p == '\0')
				break;
			*fname++ = p;
			if (fname >= pathvec + PVECSIZ) {
				fname--;
				break;
			}
		}
	}
	*fname = 0;			/* mark end of vector */
	if (is_pathname(cp)) {
		if (_nc_cgetset(cp) < 0) {
			return(TC_SYS_ERR);
		}
	}

	i = _nc_cgetent(&dummy, lineno, pathvec, name);

	/* ncurses' termcap-parsing routines cannot handle multiple adjacent
	 * empty fields, and mistakenly use the last valid cap entry instead of
	 * the first (breaks tc= includes)
	 */
	if (i >= 0) {
		char *pd, *ps, *tok;
		int endflag = FALSE;
		char *list[1023];
		size_t n, count = 0;

		pd = bp;
		ps = dummy;
		while (!endflag && (tok = get_tc_token(&ps, &endflag)) != 0) {
			bool ignore = FALSE;

			for (n = 1; n < count; n++) {
				char *s = list[n];
				if (s[0] == tok[0]
				 && s[1] == tok[1]) {
					ignore = TRUE;
					break;
				}
			}
			if (ignore != TRUE) {
				list[count++] = tok;
				pd = copy_tc_token(pd, tok, TBUFSIZ - (2+pd-bp));
				if (pd == 0) {
					i = -1;
					break;
				}
				*pd++ = ':';
				*pd = '\0';
			}
		}
	}

	FreeIfNeeded(dummy);
	FreeIfNeeded(the_source);
	the_source = 0;

	/* This is not related to the BSD cgetent(), but to fake up a suitable
	 * filename for ncurses' error reporting.  (If we are not using BSD
	 * cgetent, then it is the actual filename).
	 */
	if (i >= 0) {
		the_source = malloc(strlen(pathvec[i]) + 1);
		if (the_source != 0)
			*sourcename = strcpy(the_source, pathvec[i]);
	}

	return(i);
}
#endif /* USE_BSD_TGETENT */
#endif /* USE_GETCAP */

int _nc_read_termcap_entry(const char *const tn, TERMTYPE *const tp)
{
	int found = FALSE;
	ENTRY	*ep;
#if USE_GETCAP_CACHE
	char	cwd_buf[PATH_MAX];
#endif
#if USE_GETCAP
	char	tc[TBUFSIZ];
	static char	*source;
	static int lineno;

	/* we're using getcap(3) */
	if (_nc_tgetent(tc, &source, &lineno, tn) < 0)
		return (ERR);

	_nc_curr_line = lineno;
	_nc_set_source(source);
	_nc_read_entry_source((FILE *)0, tc, FALSE, FALSE, NULLHOOK);
#else
	/*
	 * Here is what the 4.4BSD termcap(3) page prescribes:
	 *
	 * It will look in the environment for a TERMCAP variable.  If found,
	 * and the value does not begin with a slash, and the terminal type
	 * name is the same as the environment string TERM, the TERMCAP string
	 * is used instead of reading a termcap file.  If it does begin with a
	 * slash, the string is used as a path name of the termcap file to
	 * search.  If TERMCAP does not begin with a slash and name is
	 * different from TERM, tgetent() searches the files $HOME/.termcap and
	 * /usr/share/misc/termcap, in that order, unless the environment
	 * variable TERMPATH exists, in which case it specifies a list of file
	 * pathnames (separated by spaces or colons) to be searched instead.
	 *
	 * It goes on to state:
	 *
	 * Whenever multiple files are searched and a tc field occurs in the
	 * requested entry, the entry it names must be found in the same file
	 * or one of the succeeding files.
	 *
	 * However, this restriction is relaxed in ncurses; tc references to
	 * previous files are permitted.
	 *
	 * This routine returns 1 if an entry is found, 0 if not found, and -1
	 * if the database is not accessible.
	 */
	FILE	*fp;
#define MAXPATHS	32
	char	*tc, *termpaths[MAXPATHS];
	int 	filecount = 0;
	bool	use_buffer = FALSE;
	char	tc_buf[1024];
	char	pathbuf[PATH_MAX];

	if ((tc = getenv("TERMCAP")) != 0)
	{
		if (is_pathname(tc))	/* interpret as a filename */
		{
			termpaths[0] = tc;
			termpaths[filecount = 1] = 0;
		}
		else if (_nc_name_match(tc, tn, "|:")) /* treat as a capability file */
		{
			use_buffer = TRUE;
			(void) sprintf(tc_buf, "%.*s\n", (int)sizeof(tc_buf)-2, tc);
		}
		else if ((tc = getenv("TERMPATH")) != 0)
		{
			char    *cp;

			for (cp = tc; *cp; cp++)
			{
				if (*cp == ':')
					*cp = '\0';
				else if (cp == tc || cp[-1] == '\0')
				{
					if (filecount >= MAXPATHS - 1)
						return(-1);

					termpaths[filecount++] = cp;
				}
			}
			termpaths[filecount] = 0;
		}
	}
	else	/* normal case */
	{
		char	envhome[PATH_MAX], *h;

		filecount = 0;

		/*
		 * Probably /etc/termcap is a symlink to /usr/share/misc/termcap.
		 * Avoid reading the same file twice.
		 */
		if (access("/etc/termcap", R_OK) == 0)
			termpaths[filecount++] = "/etc/termcap";
		else if (access("/usr/share/misc/termcap", R_OK) == 0)
			termpaths[filecount++] = "/usr/share/misc/termcap";

		if ((h = getenv("HOME")) != (char *)NULL)
		{
		/* user's .termcap, if any, should override it */
		    (void) strncpy(envhome, h, PATH_MAX - 10);
		envhome[PATH_MAX - 10] = '\0';
		(void) sprintf(pathbuf, "%s/.termcap", envhome);
		termpaths[filecount++] = pathbuf;
		}

		termpaths[filecount] = 0;
	}

	/* parse the sources */
	if (use_buffer)
	{
		_nc_set_source("TERMCAP");

		/*
		 * We don't suppress warning messages here.  The presumption is
		 * that since it's just a single entry, they won't be a pain.
		 */
		_nc_read_entry_source((FILE *)0, tc_buf, FALSE, FALSE, NULLHOOK);
	} else {
		int	i;

		for (i = 0; i < filecount; i++) {

			T(("Looking for %s in %s", tn, termpaths[i]));
			if ((fp = fopen(termpaths[i], "r")) != (FILE *)0)
			{
				_nc_set_source(termpaths[i]);

				/*
				 * Suppress warning messages.  Otherwise you
				 * get 400 lines of stuff from archaic termcap
				 * files as ncurses complains about all the
				 * obsolete capabilities.
				 */
				_nc_read_entry_source(fp, (char*)0, FALSE, TRUE, NULLHOOK);

				(void) fclose(fp);
			}
		}
	}
#endif /* USE_GETCAP */

	if (_nc_head == 0)
		return(ERR);

	/* resolve all use references */
	_nc_resolve_uses();

	/* find a terminal matching tn, if we can */
#if USE_GETCAP_CACHE
	if (getcwd(cwd_buf, sizeof(cwd_buf)) != 0)
	{
		_nc_set_writedir((char *)0); /* note: this does a chdir */
#endif
		for_entry_list(ep) {
			if (_nc_name_match(ep->tterm.term_names, tn, "|:"))
			{
				/*
				 * Make a local copy of the terminal
				 * capabilities.  Free all entry storage except
				 * the string table for the loaded type (which
				 * we disconnected from the list by NULLing out
				 * ep->tterm.str_table above).
				 */
				memcpy(tp, &ep->tterm, sizeof(TERMTYPE));
				ep->tterm.str_table = (char *)0;

				/*
				 * OK, now try to write the type to user's
				 * terminfo directory.  Next time he loads
				 * this, it will come through terminfo.
				 *
				 * Advantage:  Second and subsequent fetches of
				 * this entry will be very fast.
				 *
				 * Disadvantage:  After the first time a
				 * termcap type is loaded by its user, editing
				 * it in the /etc/termcap file, or in TERMCAP,
				 * or in a local ~/.termcap, will be
				 * ineffective unless the terminfo entry is
				 * explicitly removed.
				 */
#if USE_GETCAP_CACHE
				(void) _nc_write_entry(tp);
#endif
				found = TRUE;
				break;
			}
		}
#if USE_GETCAP_CACHE
		chdir(cwd_buf);
	}
#endif

	_nc_free_entries(_nc_head);
	return(found);
}
#else
extern	void _nc_read_termcap(void);
	void _nc_read_termcap(void) { }
#endif	/* PURE_TERMINFO */
